/*
 * Neuron.java
 * This file is part of the <a href="http://code.google.com/p/pux">PUX Framework</a>
 *
 * Copyright 2008 by <a href="mailto:jfendler@polytechnic.edu.na">Jens Fendler</a>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package de.teamskill.pux.library.ai;

import de.teamskill.pux.engine.AbstractNode;
import de.teamskill.pux.engine.InputPort;
import de.teamskill.pux.engine.OutputPort;

/**
 * Neuron - represents one neuron in a neuronal network
 * 
 * @author <a href="mailto:jfendler@polytechnic.edu.na">Jens Fendler</a>
 * 
 */
public class Neuron extends AbstractNode {

	/**
	 * IN_INPUT
	 */
	public static final String IN_INPUT = "IN";

	/**
	 * CONFIG_BIAS
	 */
	public static final String CONFIG_BIAS = "bias";

	/**
	 * CONFIG_WEIGHT
	 */
	public static final String CONFIG_WEIGHT = "weight";

	/**
	 * DEFAULT_BIAS
	 */
	public static final double DEFAULT_BIAS = -1.0;

	/**
	 * DEFAULT_WEIGHT
	 */
	public static final double DEFAULT_WEIGHT = 1.0;

	/**
	 * OUT
	 */
	public static final String OUT = "OUT";

	/**
	 * bias
	 */
	private InputPort bias;

	/**
	 * out
	 */
	private OutputPort out;

	/**
	 * in
	 */
	private InputPort[] in;

	/**
	 * weight
	 */
	private InputPort[] weights;

	/**
	 * lastResult
	 */
	private int lastResult;

	/**
	 * inputChannels
	 */
	private int inputChannels;

	/**
	 * Neuron Constructor
	 * 
	 */
	public Neuron() {
		this("NEURON" + nr++, false, 1);
	}

	/**
	 * Neuron Constructor
	 * 
	 * @param nodeName
	 * @param continuous
	 */
	public Neuron(String nodeName, boolean continuous, int inputChannels) {
		super(nodeName, continuous);
		this.inputChannels = inputChannels;
	}

	/**
	 * @see de.teamskill.pux.engine.AbstractNode#compute()
	 */
	public void compute() {
		double biasValue = ((Double) (bias.fetchData())).doubleValue();
		double sum = 0.0;
		for (int i = 0; i < inputChannels; i++) {
			double wi = ((Double) (weights[i].fetchData())).doubleValue();
			int xi = ((Integer) (getInputPort(IN_INPUT + i)).fetchData()).intValue();
			sum += (wi * xi);
		}
		int result = sum > biasValue ? 1 : 0;
		out.pushData(new Integer(result));
		// remember the last output result for possible future learning
		lastResult = result;
	}

	/**
	 * learn - adjust weights by comparing the last expected output as given
	 * with the last output result as calculated in the compute() method
	 * 
	 * @param lastExpectedOutput
	 * @param deltaW
	 */
	public void learn(int lastExpectedOutput, double deltaW) {
		// TODO implementa learning with backpropagation
	}

	/**
	 * adjustWeight
	 * 
	 * @param input
	 * @param newWeight
	 */
	public void adjustWeight(int inputChannel, double newWeight) {
		weights[inputChannel].setData(new Double(newWeight));
	}

	/**
	 * @see de.teamskill.pux.engine.AbstractNode#createPorts()
	 */
	protected void createPorts() {
		for (int i = 0; i < inputChannels; i++) {
			in[i] = addInputPort(IN_INPUT + i, Integer.class, true);
			weights[i] = addConfigurationPort(CONFIG_WEIGHT + i, Double.class, new Double(DEFAULT_WEIGHT));
		}
		bias = addConfigurationPort(CONFIG_BIAS, Double.class, new Double(DEFAULT_BIAS));
		out = addOutputPort(new OutputPort(OUT, this, Integer.class));
	}

	/**
	 * JavaBean-style getter-method for bias
	 * 
	 * @return the bias
	 */
	public InputPort getBias() {
		return bias;
	}

	/**
	 * /** JavaBean-style setter-method for bias
	 * 
	 * @param bias
	 *            the bias to set
	 */
	public void setBias(InputPort bias) {
		this.bias = bias;
	}

	/**
	 * JavaBean-style getter-method for out
	 * 
	 * @return the out
	 */
	public OutputPort getOut() {
		return out;
	}

	/**
	 * /** JavaBean-style setter-method for out
	 * 
	 * @param out
	 *            the out to set
	 */
	public void setOut(OutputPort out) {
		this.out = out;
	}

	/**
	 * JavaBean-style getter-method for in
	 * 
	 * @return the in
	 */
	public InputPort[] getIn() {
		return in;
	}

	/**
	 * /** JavaBean-style setter-method for in
	 * 
	 * @param in
	 *            the in to set
	 */
	public void setIn(InputPort[] in) {
		this.in = in;
	}

	/**
	 * JavaBean-style getter-method for weights
	 * 
	 * @return the weights
	 */
	public InputPort[] getWeights() {
		return weights;
	}

	/**
	 * /** JavaBean-style setter-method for weights
	 * 
	 * @param weights
	 *            the weights to set
	 */
	public void setWeights(InputPort[] weights) {
		this.weights = weights;
	}

	/**
	 * JavaBean-style getter-method for lastResult
	 * 
	 * @return the lastResult
	 */
	public int getLastResult() {
		return lastResult;
	}

	/**
	 * /** JavaBean-style setter-method for lastResult
	 * 
	 * @param lastResult
	 *            the lastResult to set
	 */
	public void setLastResult(int lastResult) {
		this.lastResult = lastResult;
	}

	/**
	 * JavaBean-style getter-method for inputChannels
	 * 
	 * @return the inputChannels
	 */
	public int getInputChannels() {
		return inputChannels;
	}

	/**
	 * /** JavaBean-style setter-method for inputChannels
	 * 
	 * @param inputChannels
	 *            the inputChannels to set
	 */
	public void setInputChannels(int inputChannels) {
		this.inputChannels = inputChannels;
	}

}
